//--------------------------------------------------------------------
// functions to support programs in DK-BASIC
//--------------------------------------------------------------------
#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dkbasic.h"
#include "dkvariables.h"
#include "dkstring.h"
#include "dkprogram.h"
#include "dkinterpreter.h"
#include "dkterm.h"
#include "dkstatements.h"
#include "dkevents.h"
#include "dktimer.h"

//--------------------------------------------------------------------

// dkprogram is a linked list of struct _dkprogline
struct _dkprogline *dkprogram=NULL;

uint16_t dkPC;							// program counter
uint16_t dkprevPC;						// pc of previous line
uint8_t dkstatus;						// status - holds error codes / goto/gosub indicators
uint8_t dktrace;						// trace (tron/troff)

//--------------------------------------------------------------------
// dkrunprogram - execute program - start at line # in global dkPC
uint16_t dkrunprogram( void )
{
	struct _dkprogline *programline;
	struct _dkfornext *fornextptr;
	struct _dkwhilewend *whilewendptr;
	struct _dkgosub *gosubptr;
	struct _dk_errorhandler *errorhandlerptr;
	int i;
	uint16_t handler_linenum;
	

	if( dkPC == 0 )							// when dkPC=0, we are entering the program and will do some initialization first
	{										// dkfornextstack, dkwhilewendstack, dkgosubstack are reset
		programline = dkprogram;			// dkPC is 0, start at first line of program 

		while( dkfornextstack )				// free any memory that was used by dkfornextstack
		{
			fornextptr = dkfornextstack->next;
			free( dkfornextstack->var->name );
			free( dkfornextstack->var );
			free( dkfornextstack );
			dkfornextstack = fornextptr;
		}
		while( dkwhilewendstack )			// free any memory that was used by dkwhilewendstack
		{
			whilewendptr = dkwhilewendstack->next;
			free( dkwhilewendstack );
			dkwhilewendstack = whilewendptr;
		}
		while( dkgosubstack )				// free any memory that was used by dkgosubstack
		{
			gosubptr = dkgosubstack->next;
			free( dkgosubstack );
			dkgosubstack = gosubptr;
		}
		while( dk_errorhandlers	)
		{
			errorhandlerptr = dk_errorhandlers->next;
			free( dk_errorhandlers );
			dk_errorhandlers = errorhandlerptr;
		}
		dkeventflags=0;		// bitmapped for each type of event that is enabled
		dkeventmasks=0;
		dkevent_key=0;		// set to value of last KEY event (only most recent KEY event is kept)
		dktimer_flags=0;
		dktimer_masks=0;	
		dktimereventtick = 0x10000 - (F_CPU/1024);	// default to 1 second 

		for( i=0; i<NUM_COM_HANDLERS; i++ )
			dkevent_com_handlers[i] = 0;		// line number for handlers for COM0: and COM1:
		for( i=0; i<NUM_KEY_HANDLERS; i++ )
			dkevent_key_handlers[i] = 0;		// event handlers 1-20 for KEY()
		for( i=0; i<NUM_TIMER_HANDLERS; i++ )
			dkevent_timer_handlers[i] = 0;		// event handler for TIMER()					
	}
	else
		programline = dkgetclosestprogline( dkPC );	// get line to start program on




	while( programline )
	{											// line not found - program finished
		while( io_ctrl_char == DKKEY_PAUSE )
			;									// if pause was pressed, wait

		if( io_ctrl_char == DKKEY_BREAK )
		{
			while( _kbhit() )
			{
				if( _getch() == DKKEY_BREAK )
					break;
			}
			dkstatus = DKSTATUS_BREAK;
			io_ctrl_char = 0;					// reset uart_ctrl_char (it's been noticed)
			return 0;							// ctrl-C was received - break out of program
		}

		if( dkeventflags & dkeventmasks )		// check if an event has been flagged and has an active mask
		{
			handler_linenum = dkhandle_event();
			if( handler_linenum )
			{
				programline = dkgetclosestprogline( handler_linenum );
				if( !programline )
					return 0;						// error: undefined line number
				if( programline->num != handler_linenum )
					return 0;						// error: undefined line number
			}
		}

		dkprevPC = dkPC;
		dkPC = programline->num;				// keep track of current PC
		
		dkinterpretline( programline->line );	// interpret this line of the program

		switch( dkstatus )
		{
			case DKSTATUS_OK:
				programline = programline->next;	// no change of PC, step to next line
				break;

			case DKSTATUS_END:
				programline = NULL;				// end program
				break;

			case DKSTATUS_GOTO:
				programline = dkgetclosestprogline( dkPC );
				if( !programline )
					return 0;						// error: undefined line number
				if( programline->num != dkPC )
					return 0;						// error: undefined line number
				break;

			case DKSTATUS_RETURN:
				programline = dkgetclosestprogline( dkPC );
				if( !programline )
					return 0;						// error: undefined line number
				programline = programline->next;	// no change of PC, step to next line
				break;

			case DKSTATUS_SKIPTONEXT:
				programline = programline->next;	// no change of PC, step to next line
				break;				// 'FOR without NEXT error will be displayed

			default:
				return 0;
				break;
		}

	}	// while( programline )
	dkprevPC = dkPC;
	dkPC = 0;							// PC = 0 (program reached end)


	return 0;
}

//--------------------------------------------------------------------
// dksetprogline - set line for linenum num - add line if it doesn't exist
struct _dkprogline *dksetprogline( uint16_t num, char *line )
{
	struct _dkprogline *lineptr;
	struct _dkprogline *newptr;
	char *newline;


	lineptr = dkgetclosestprogline( num );
	if( !lineptr )
	{									// add lineptr to start of list
		if( !strlen(line) )
		{
			dkstatus = DKSTATUS_UNDEFINEDLINENUMBER;
			return NULL;				// setting line to blank line - don't add item into list
		}

		lineptr = (struct _dkprogline *)malloc( sizeof( struct _dkprogline ) );
		if( !lineptr )
		{
			dkstatus = DKSTATUS_OUTOFMEMORY;
			return NULL;				// malloc error
		}

		if( dkprogram )
			lineptr->next = dkprogram;
		else
			lineptr->next = NULL;
		dkprogram = lineptr;			// link lineptr to beginning of list
	}
	else if( lineptr->num == num )
	{									// found matching line number - replace line
		if( !strlen(line) )
		{								// remove line from list
			newptr = dkgetclosestprogline( num-1 );	// get prev item
			if( newptr )
				newptr->next = lineptr->next;	// remove item from list
			else
				dkprogram = lineptr->next;

			free( lineptr->line );		// free mem previously used by line
			free( lineptr );			// free mem previously used by item in list

			return NULL;
		}
		free( lineptr->line );			// free memory from previous line (new memory allocated below)
	}
	else
	{									// add this line after lineptr
		if( !strlen(line) )
		{
			dkstatus = DKSTATUS_UNDEFINEDLINENUMBER;
			return NULL;				// setting line to blank line - don't add item into list
		}

		newptr = (struct _dkprogline *)malloc( sizeof( struct _dkprogline ) );
		if( !newptr )
		{
			dkstatus = DKSTATUS_OUTOFMEMORY;
			return NULL;				// malloc error
		}

		newptr->next = lineptr->next;
		lineptr->next = newptr;			// link newptr into the list
		lineptr = newptr;
	}


	newline = (char *)malloc( strlen(line)+1 );
	if( !newline )
	{
		dkstatus = DKSTATUS_OUTOFMEMORY;
		return NULL;				// malloc error
	}
	strcpy( newline, line );		// copy text in line to newly allocated line

	lineptr->line = newline;
	lineptr->num = num;


	return lineptr;
}


//--------------------------------------------------------------------
// dkgetclosestprogline - return pointer to the line number less than or equal to num
struct _dkprogline *dkgetclosestprogline( uint16_t num )
{
	struct _dkprogline *listptr = dkprogram;
	struct _dkprogline *prevptr = NULL;


	while( listptr )
	{
		if( listptr->num > num )
			return prevptr;				// current line number is > what we're searching for - return previous line

		prevptr = listptr;
		listptr = listptr->next;		// point listptr to next item in list
	}


	return prevptr;						// no variable in list higher than num
}


//--------------------------------------------------------------------
int dkloadprogram( char *filename )
{
	uint16_t i;
	char s[ MAX_LINE_LENGTH+1 ];
	char t[ MAX_TOKEN_LENGTH+1 ];
	char *lptr;
	DKFILE *loadfile;
	uint8_t status=0;


	loadfile = dkfopen( filename, "r" );
	if( !loadfile )
	{
		if( dkstatus < DKSTATUS_FIRSTERROR )
			dkstatus = DKSTATUS_FILENOTFOUND;
		return 1;					// error
	}	


	while( io_ctrl_char != DKKEY_CTRLC )
	{
		for( i=0; i<MAX_LINE_LENGTH; i++ )
		{
		    if( dkfeof(loadfile) || dkferror(loadfile ) )
		    {
		        status = 4;                 // error reading from file
		        break;
		    }
		        
			s[i] = dkfgetc( loadfile );
			s[i+1] = '\0';
			
			if( io_ctrl_char == DKKEY_CTRLC )
			    break;
			
			if( s[i] == '\n' )
			{								// end of line was reached
				s[i] = '\0';				
				break;
			}
			else if( s[i] == '\0' )
				break;
			else if( s[i] < 0 )
			{
				status=2;					// signal corrupt data was read
				break;
			}
		}
		if( status || io_ctrl_char == DKKEY_CTRLC )
			break;

		if( !strlen( s ) )
			break;

		lptr = dkgettoken( s, t );
		if( !strlen(t) )
			continue;

		if( atoi(t) > 0 )
		{								// got a line number
			if( lptr[0] )				// if the line is not blank, skip over the first space
				lptr++;
			dksetprogline( atoi(t), lptr );
		}
		else
		{
//			printf_P( PSTR("err: %s\n"), s );
			status=3;
			break;
		}
	}

	dkfclose( loadfile );


	return status;
}
//--------------------------------------------------------------------
void dknewprogram( void )
{
	struct _dkprogline *programptr;


	while( dkprogram )
	{									// free memory used by dkprogram
		programptr = dkprogram->next;
		free( dkprogram );
		dkprogram = programptr;
	}

	dkclearprogram();

	dkstatus = DKSTATUS_END;			// if in a program, don't continue running (program has been free'd)

	dkprevPC = 0;


	return;
}
//--------------------------------------------------------------------
void dkclearprogram( void )
{
	struct _dkfornext *fornextptr;
	struct _dkwhilewend *whilewendptr;
	struct _dkgosub *gosubptr;
	struct _dkvariable *variableptr;


	while( dkfornextstack )				// free any memory that was used by dkfornextstack
	{
		fornextptr = dkfornextstack->next;
		free( dkfornextstack->var->name );
		free( dkfornextstack->var );
		free( dkfornextstack );
		dkfornextstack = fornextptr;
	}
	while( dkwhilewendstack )			// free any memory that was used by dkwhilewendstack
	{
		whilewendptr = dkwhilewendstack->next;
		free( dkwhilewendstack );
		dkwhilewendstack = whilewendptr;
	}
	while( dkgosubstack )				// free any memory that was used by dkgosubstack
	{
		gosubptr = dkgosubstack->next;
		free( dkgosubstack );
		dkgosubstack = gosubptr;
	}
	while( dkvariables )
	{									// free memory used by dkprogram
		if( dkvariables->e.data )
		{
			if( dkvariables->e.type == DKVAR_STRING )
				free( ((char **)dkvariables->e.data)[0] );
			free( dkvariables->e.data );
		}
		free( dkvariables->name );
		variableptr = dkvariables->next;
		free( dkvariables );
		dkvariables = variableptr;
	}
	
	return;
}
//--------------------------------------------------------------------
